/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/* 
 * Copyright (c) 2009 City University of Hong Kong
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License m_version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Author: Wilson Thong (wilsonwk@ee.cityu.edu.hk) 
 */
#ifndef POINT_TO_POINT_GRAPH_H
#define POINT_TO_POINT_GRAPH_H

#include <string>
#include <iostream>
#include <vector>
#include <set>
#include <iterator>
#include "ns3/point-to-point-channel.h"
#include "ns3/point-to-point-net-device.h"
#include "ns3/test.h"
#include "ns3/channel-list.h"


namespace ns3 {
namespace eecityu {

using namespace std;

class GraphLink;

/** 
 * \brief An API to abstract an ns-3 Node object as a graph node
 */
class GraphNode : public Object {
public:
  static TypeId GetTypeId (void);

  /// create an empty GraphNode object to be aggregated into an ns-3 Node object
  GraphNode ();    
  /**
   * \brief create a partial GraphNode object to be aggregated into an ns-3 Node object
   *
   * \param name The name of a node. It should be globally unique
   */
  explicit GraphNode (string name);
  /** 
   * \brief create a full GraphNode object and associate it to an ns-3 Node object
   *
   * \param name The name of a node. It should be globally unique
   * \param n The ns-3 Node object associated with this GraphNode
   */   
  GraphNode (string name, Ptr<Node> n);
  /// \param name The name of a node. It should be globally unique
  void SetName (string name);
  /// \param n Set the node to have this name
  void SetNode (Ptr<Node> n);
  /// \param gn Another graph node object
  bool operator< (const GraphNode& gn) const;
  /// \param gn Another graph node object
  bool operator> (const GraphNode& gn) const;
  /// \param gn Another graph node object
  bool operator== (const GraphNode& gn) const;
  /// \param gn Another graph node object
  bool operator!= (const GraphNode& gn) const;
  /// \return Get the name of a node
  string GetName () const;
  /// \return Get the node with this name
  Ptr<Node> GetNode () const;
  /// \return Get the i-th graph link attached to this node. i starts at one
  Ptr<GraphLink> GetGraphLink (int i) const;
  /// \return Get the number of graph links attached to this node
  uint32_t GetNGraphLinks () const;
  /// \return Get the node ID from its Node class API
  uint32_t GetId () const;
private:
  /// Name of a node
  string m_name;
  /// Point to a node
  Ptr<Node> m_node;

  friend class GraphNodeTestCase;
};
ostream& operator<< (ostream& os, const GraphNode& gn);

/**
 * \brief An API to abstract an ns-3 PointToPointNetDevice as an bidirectional graph link
 */
class GraphLink : public Object {
public:
  static TypeId GetTypeId (void);

  /// create an empty new GraphLink object
  GraphLink ();
  /** 
   * \brief create a complete new GraphLink object
   *
   * \param local The local node
   * \param remote The remote node
   */
  GraphLink (Ptr<GraphNode> local, Ptr<GraphNode> remote);
  /**
   * \brief compare with another GraphLink object
   *
   * The comparison treats each link as bidirectional link. So links {a,b} and {b,a} are the same.
   * Neverthless, nodes on a link are identified as local and remote. This distinction is useful
   * when GraphNode returns all its links
   *
   * \param gl Another GraphLink object
   */
  bool operator<  (const GraphLink& gl) const;
  bool operator>  (const GraphLink& gl) const;
  bool operator== (const GraphLink& gl) const;
  bool operator!= (const GraphLink& gl) const;
  bool operator<= (const GraphLink& gl) const;
  bool operator>= (const GraphLink& gl) const;
  /**
   * \brief Set the to node and from node for this link
   *
   * \param local The local note
   * \param remote The remote node
   */
  void SetNode (Ptr<GraphNode> local, Ptr<GraphNode> remote);
  /// \brief Swap the local node and remote node of this link
  void SwapLocalRemoteNodes ();
  /// \return Get the from node of this link
  Ptr<GraphNode> GetNodeLocal () const;
  /// \return Get the to node of this link
  Ptr<GraphNode> GetNodeRemote () const;
  /// \return Get the physical point to point channel for this abstract graphical link
  Ptr<PointToPointChannel> GetPointToPointChannel () const;
private:
  /// A GraphNode identified as local
  Ptr<GraphNode> m_nodeLocal;
  /// A GraphNode identified as remote
  Ptr<GraphNode> m_nodeRemote;

  friend class GraphLinkTestCase;
};
ostream& operator<< (ostream& os, const GraphLink& gl);

/**
 * \brief The class aids creating unidirectional graphs 
 *
 * It uses GraphNode and GraphLink classes to create a graph. The actual graph
 * is constructed by ns-3 networking objects (PointToPointChannel, PointToPointNetDevice, etc),
 * but its API abstracts out all these technological details. Only the simple graph elements
 * are presented.
 */
class PointToPointGraph
{
public:
  PointToPointGraph (void);

  /**
   * \brief Add an unidirectional link into this topology
   *
   * A new node is created if there is no such node with the specified name. Parallel links 
   * and self-loop links are not allowed. Node name has to be unique and this topology class 
   * uses name as an unique ID for each node
   *
   * \param name1 The name of a GraphNode
   * \param name2 The name of an other GraphNode
   *
   * \return True if the link is added successfully; false otherwise
   */
   bool AddLink (string name1, string name2);
   /**
    * \brief Get the GraphNode from a name
    *
    * \param name The name of the GraphNode to be found
    * \return The GraphNode if the name is found; NULL otherwise
    */
   Ptr<GraphNode> GetNode (string name) const;
   /**
    * \brief Get a link from the graph
    *
    * \param n1 A GraphNode
    * \param n2 An other GraphNode
    *
    * \return A GraphLink is returned if there exists a link between the two specified GraphNodes; false otherwise
    */
   Ptr<GraphLink> GetLink (Ptr<GraphNode> gn1, Ptr<GraphNode> gn2) const;
   /**
    * \brief Get a link from the graph
    *
    * \param name1 The name of a GraphNode
    * \param name2 The name of an other GraphNode
    *
    * \return A GraphLink is returned if there exists a link between the two specified GraphNodes; false otherwise
    */
   Ptr<GraphLink> GetLink (const char* name1, const char* name2) const;
   /// \return Get the number of nodes from this graph
   uint32_t GetNodesN () const;
   /**
    * \brief Get all GraphLinks from this graph, and the links are all treated as bidirectional links
    *
    * \param outIter Output parameter. An insert iterator of a STL container containing Ptr<GraphLink>.
    * All GraphLinks are inserted into a STL container via this insert iterator
    *
    \ return The number of GraphLinks of this graph
    */
   template <typename Container> void GetAllLinks (insert_iterator<Container> outIter) const;
   
private:
  class GraphNodeLess {
  public:
    /// \brief To sort Ptr<GraphNode> objects according to their names. This class is used when defining the sorting order of some STL containers
    bool operator() (const Ptr<GraphNode>& n1, const Ptr<GraphNode>& n2) const;
  };
  class GraphLinkLess {
  public:
    /// \brief To sort Ptr<GraphLink> objects according to their names. This class is used when defining the sorting order of some STL containers
    bool operator() (const Ptr<GraphLink>& glink1, const Ptr<GraphLink>& glink2) const;
  }; 
public:
  typedef set<Ptr<GraphNode>, GraphNodeLess> GraphNodeSet_t;
  typedef set<Ptr<GraphLink>, GraphLinkLess> GraphLinkSet_t;
private:
  /// the set of all nodes in this topology
  GraphNodeSet_t m_graphNodeSet;

  friend ostream& operator<< (ostream& os, const PointToPointGraph& bt);
  friend class PointToPointGraphTestCase;
};
ostream& operator<< (ostream& os, const PointToPointGraph& bt);

class PointToPointGraphTestCase : public TestCase 
{
public:
  PointToPointGraphTestCase ();
  virtual bool DoRun ();
};

class GraphNodeTestCase : public TestCase
{
public:
  GraphNodeTestCase ();
  virtual bool DoRun ();
};

class GraphLinkTestCase : public TestCase
{
public:
  GraphLinkTestCase ();
  virtual bool DoRun ();
};

class BaseTopologyTestSuite : public TestSuite
{
public:
  BaseTopologyTestSuite ();
};

/////////////////////////////////////////////////////
// PointToPointGraph class template implementation //
/////////////////////////////////////////////////////

template <typename Container> void 
PointToPointGraph::GetAllLinks (insert_iterator<Container> outIter) const
{
  typedef set<Ptr<GraphLink>, GraphLinkLess> GraphLinkSet_t;
  GraphLinkSet_t graphLinkSet;

  // find out all links that are added into this graph
  for (GraphNodeSet_t::const_iterator it = m_graphNodeSet.begin (); it != m_graphNodeSet.end (); it++) {
    Ptr<GraphNode> gnode = *it;
    for (uint32_t i=0; i<gnode->GetNGraphLinks (); i++) {
      Ptr<GraphLink> glink = gnode->GetGraphLink (i);
      bool isNew = graphLinkSet.insert (glink).second;
      // insert the glink into the output iterator outIter only if the glink is not duplicated.
      // Duplication can occurs when, for example, a link {a,b} is already outputed and now 
      // node 'b' returns the glink {b,a}
      if (isNew)
        *outIter = glink;
    }
  }
}

} // namespace eecityu
} // namespace ns3

#endif